<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="test"></div>
    <button id="button">按钮</button>
    <script>
      let addGlobalWatchers = [];
      let globalWatcher = null;
      class Dep {
        constructor() {
          this.deps = null;
        }
        addDep(watcher) {
          this.deps = watcher;
        }
        notify(text) {
          this.deps.notifyWatcher(text);
        }
      }
      class Watcher {
        constructor(node) {
          this.node = node;
        }
        notifyWatcher(text) {
          this.node.textContent = text;
        }
      }
      class Mvvm {
        constructor(options) {
          this._data = options.data;
          this._$el = document.querySelector(options.el);

          this.defineReactive(this._data);
          this.compile(this._$el.childNodes);
        }
        defineProps(data, key, val) {
          var dep = new Dep();
          Object.defineProperty(data, key, {
            enumerable: true,
            configurable: false,
            get: () => {
              dep.addDep(globalWatcher);
              addGlobalWatchers.push(globalWatcher);
              return val;
            },
            set: (newVal) => {
              val = newVal;
              console.log("set");
              dep.notify(newVal);
              // addGlobalWatchers.forEach((w) => {
              //   w.notifyWatcher(newVal);
              // });
            },
          });
        }
        defineReactive(data) {
          Object.keys(data).forEach((k) => {
            if (typeof data[k] == "object") {
              this.defineReactive(data[k]);
            } else {
              this.defineProps(data, k, data[k]);
            }
          });
        }
        compile(childNodes) {
          var reg = /\{\{(.*)\}\}/;
          Array.from(childNodes).forEach((node) => {
            var text = node.textContent;
            if (node.nodeType === 3 && reg.test(text)) {
              // 添加watcher
              globalWatcher = new Watcher(node, RegExp.$1.trim(), function () {
                this.compileText;
              });
              this.compileText(node, RegExp.$1.trim());
            }
            if (node.childNodes && node.childNodes.length) {
              this.compile(node.childNodes);
            }
          });
        }
        compileText(node, key) {
          node.textContent = this._getVal(key);
        }
        _getVal(key) {
          var val = "";
          for (var k in this._data) {
            if (key == k) {
              if (typeof this._data[k] == "object") {
                this._getVal(this._data[k]);
              } else {
                val = this._data[k];
              }
            }
          }
          return val;
        }
      }
    </script>

    <!-- 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 -->
    <script>
      class Element {
        constructor(type, props, children) {
          this.type = type;
          this.props = props;
          this.children = children;
        }
      }
      function setAttr(node, key, value) {
        switch (key) {
          case "value":
            if (
              node.tagName.toUpperCase() === "INPUT" ||
              node.tagName.toUpperCase() === "TEXTAREA"
            ) {
              node.value = value;
            } else {
              node.setAttribute(key, value);
            }
            break;
          case "style":
            node.style.cssText = value;
            break;
          default:
            node.setAttribute(key, value);
            break;
        }
      }
      //type类型，Prop属性,children子成员
      function createElement(type, props, children) {
        return new Element(type, props, children);
      }
      //render方法可以将虚拟dom转化为真实的dom
      function render(eleObj) {
        let el = document.createElement(eleObj.type);
        for (let key in eleObj.props) {
          //循环将属性添加到dom上去
          setAttr(el, key, eleObj.props[key]);
        }
        //拿到所有的子孙元素children
        eleObj.children.forEach((child) => {
          child =
            child instanceof Element
              ? render(child)
              : document.createTextNode(child);
          el.appendChild(child);
        });
        return el;
      }
      function renderDom(el, target) {
        target.appendChild(el);
      }
    </script>
    <script>
      function diff(oldTree, newTree) {
        let patches = {};
        let index = 0;
        //递归树，比较后的结果放到补丁包中
        walk1(oldTree, newTree, index, patches);
        return patches;
      }
      function diffAttr(oldAttrs, newAttrs) {
        let patch = {};
        //直接判断老的属性是否跟新的属性一样，如果不一样的话，则放到patch中
        for (let key in oldAttrs) {
          if (oldAttrs[key] !== newAttrs[key]) {
            patch[key] = newAttrs[key]; //有可能是undefined
          }
        }
        //判断是否有新增的属性
        for (let key in newAttrs) {
          //老节点没有新节点的属性
          if (!oldAttrs.hasOwnProperty(key)) {
            patch[key] = newAttrs[key];
          }
        }
        //将属性不同的对象返回
        return patch;
      }
      const ATTRS = "ATTRS";
      const TEXT = "TEXT";
      const REMOVE = "REMOVE";
      const REPLACE = "REPLACE";
      let Index = 0;
      function diffChildren(oldChildren, newChildren, patches) {
        //比较老节点的第一个和新的第一个
        oldChildren.forEach((child, idx) => {
          walk1(child, newChildren[idx], ++Index, patches);
        });
      }
      function isString(node) {
        return Object.prototype.toString.call(node) === "[object String]";
      }
      function walk1(oldNode, newNode, index, patches) {
        let currentPatch = [];
        if (!newNode) {
          currentPatch.push({ type: REMOVE, index: index });
        } else if (isString(oldNode) && isString(newNode)) {
          if (oldNode !== newNode) {
            //判断文本是否一致
            currentPatch.push({ type: TEXT, text: newNode });
          }
        } else if (oldNode.type === newNode.type) {
          //先比父元素，看看类型相同不相同
          //比父元素属性,返回一个变化的对象
          let attrs = diffAttr(oldNode.props, newNode.props);
          if (Object.keys(attrs).length > 0) {
            currentPatch.push({ type: ATTRS, attrs });
          }
          //如果有儿子节点的话，那么遍历儿子节点接着比较
          diffChildren(oldNode.children, newNode.children, patches);
        } else {
          //替换的情况
          currentPatch.push({ type: REPLACE, newNode: newNode });
        }
        //当前的父元素确实有补丁的话，放入第一层的补丁包中。
        if (currentPatch.length > 0) {
          patches[index] = currentPatch;
        }
      }
    </script>
    <script>
      let allPathes;
      let index = 0;
      function setAttr(node, key, value) {
        switch (key) {
          case "value":
            if (
              node.tagName.toUpperCase() === "INPUT" ||
              node.tagName.toUpperCase() === "TEXTAREA"
            ) {
              node.value = value;
            } else {
              node.setAttribute(key, value);
            }
            break;
          case "style":
            node.style.cssText = value;
            break;
          default:
            node.setAttribute(key, value);
            break;
        }
      }
      function patch(node, patches) {
        //打补丁的过程
        allPathes = patches;
        walk(node);
      }
      function walk(node) {
        let currrentPatch = allPathes[index++];
        let childNodes = node.childNodes;
        childNodes.forEach((child) => walk(child));
        if (currrentPatch) {
          doPatch(node, currrentPatch);
        }
      }
      function doPatch(node, patches) {
        patches.forEach((patch) => {
          switch (patch.type) {
            case "ATTRS":
              for (let key in patch.attrs) {
                let value = patch.attrs[key];
                if (value) {
                  setAttr(node, key, value);
                } else {
                  node.removeAttribute(key);
                }
              }
              break;
            case "TEXT":
              node.textContent = patch.text;
              break;
            case "REPLACE":
              let newNode =
                patch.newNode instanceof Element
                  ? render(patch.newNode)
                  : document.creaateTextNode(patch.newNode);
              node.parentNode.replaceChild(newNode, node);
              break;
            case "REMOVE":
              node.parentNode.removeChild(node);
              break;
            default:
              break;
          }
        });
      }
    </script>
    <script>
      let root = document.getElementById("test");
      window.root = root;
      let vertualDom1 = createElement("ul", { class: "list" }, [
        createElement("li", { class: "item" }, ["{{name}}"]),
        createElement("li", { class: "item" }, ["{{age}}"]),
      ]);
      let vertualDom2 = createElement("ul", { class: "list-group" }, [
        createElement("li", { class: "item" }, ["1"]),
        createElement("li", { class: "item" }, ["b"]),
        createElement("li", { class: "item" }, ["3"]),
      ]);
      //用render转换VDOM1为真实dom
      console.log(vertualDom1);
      let el = render(vertualDom1);
      //渲染真实dom
      renderDom(el, window.root);
      document.getElementById("button").addEventListener("click", function () {
        //调用diff算法，得到补丁包
        let patches = diff(vertualDom1, vertualDom2);
        //给元素打补丁，重新更新视图
        patch(el, patches);
      });
    </script>
    <script>
      var vm = new Mvvm({
        el: "#test",
        data: {
          name: "test",
          age: 1,
        },
      });
      Object.keys(vm._data).forEach((key) => {
        Object.defineProperty(vm, key, {
          enumerable: true,
          configurable: false,
          set: (newVal) => {
            vm._data[key] = newVal;
          },
          get: () => vm._data[key],
        });
      });
      setInterval(() => {
        vm.name = "Name：" + Math.random();
        vm.age = "Age：" + Math.random();
      }, 2000);
    </script>
  </body>
</html>
